package com.xp.basic.heima.nio.netty.simpledemo.chat.protocol;

import com.xp.basic.heima.nio.netty.simpledemo.chat.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.List;

/**
 * #################################################################################################
 * ##########                  【自定义】消息 编解码 类   【 支持@Sharable 】                   ########
 * ##########   父类 MessageToMessageCodec 认为是完整的信息 【所以必须保证上一个处理器是 帧解码器】 ########
 * #################################################################################################
 * 相当于两个handler合二为一，【既能入站 也能做出站处理】
 *  <b>魔数     </b>，用来在第一时间判定是否是无效数据包
 *  <b>版本号   </b>，可以支持协议的升级
 *  <b>序列化算法</b>，消息正文到底采用哪种序列化反序列化方式，可以由此扩展，例如：json、protobuf、hessian、jdk
 *  <b>指令类型  </b>，是登录、注册、单聊、群聊... 跟业务相关
 *  <b>请求序号  </b>，为了双工通信，提供异步能力
 *  <b>正文长度  </b>
 *  <b>消息正文  </b>
 */
// 必须和 LengthFieldBasedFrameDecoder 一起使用，确保接到的 ByteBuf 消息是完整的
// 写这个类 肯定的认为 上一个处理器 是 帧解码器，所以不用考虑半包黏包问题，直接解码拿数据
@Slf4j
@ChannelHandler.Sharable
public class MessageCodec extends MessageToMessageCodec<ByteBuf, Message> {


    /**
     * 编码将Message 编码到 ByteBuf 中
     * @param ctx
     * @param message
     * @param list
     * @throws Exception
     */
    @Override
    public void encode(ChannelHandlerContext ctx, Message message, List<Object> list) throws Exception {

        ByteBuf buffer = ctx.alloc().buffer();

        //魔数 4 字节
        buffer.writeBytes(new byte[]{1,2,3,4});
        //消息版本号  1 字节
        buffer.writeByte(message.getVersion());
        //序列化方式  1字节
        buffer.writeByte(message.getSerializeType());
        //消息类型    1字节
        buffer.writeByte(message.getMessageType());
        //消息序号    4字节
        buffer.writeInt(message.getSquenceId());
        //发送消息一般是八的倍数 ，如果这里不够的话，我们把他补齐
        buffer.writeByte(0xff);
        //消息内容长度  4字节
        byte[] byteArray = null;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)){
            oos.writeObject(message);
            byteArray = bos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //消息内容长度
        buffer.writeInt(byteArray.length);
        //消息正文
        buffer.writeBytes(byteArray);

        System.out.println("发送的对象字节数为："+byteArray.length);
        /**
         * 加入List 方便传递给 下一个Handler
         */
        list.add(buffer);
    }


    /**
     * 解码 将 ByteBuf 解码成 Message
     * @param ctx
     * @param byteBuf
     * @param list
     * @throws Exception
     */
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int magicNum = in.readInt();
        byte version = in.readByte();
        byte serializerAlgorithm = in.readByte(); // 0 或 1
        byte messageType = in.readByte(); // 0,1,2...
        int sequenceId = in.readInt();
        in.readByte();
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Message message = (Message) ois.readObject();

        out.add(message);
    }
}

